# # TCP编程
# #  Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络连接”，而打开一个sockrt·
# # 需要知道目标的IP地址和端口号，再指定协议类型即可
# # 举个例子，当我们在浏览器中访问新浪时，我们自己的计算机是客户端，浏览器会主动向新浪服务器发起连接，如果一切顺利，新浪的服务器接受了我们的连接，一个TCP连接就建立起来的，后面的通信就是发送网页内容了。。
#
# # 导入socket库：
# import socket
# # 创建一个socket:
# import threading
#
# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# # 建立连接：
# s.connect(('www.sina.com.cn', 80))
# # 创建Socket时，AF_INET指定使用IPv4协议，如果要用更先进的IPv6,就指定为AF_INET6
# # 客户端要主动发起TCP连接，必须知道服务器的IP地址和端口号。新浪网站的IP地址可以用域名www.sina.com.cn自动转换到IP地址，但是怎么知道新浪服务器的端口号呢？
#
# #答案是作为服务器，提供什么样的服务，端口号就必须固定下来，由于我们想要访问网页，因此新浪提供网页服务的服务器必须把端口号固定在80端口，因为80端口是Web服务的标准端口。其他服务都有对应的标准端口号，例如SMTP服务是25端口，FTP服务是21端口，等等。端口号小于1024的是Internet标准服务的端口，端口号大于1024的，可以任意使用。
# # 因此，我们连接新浪服务器的代码如下：
# s.connect(('www.sina.com.cn',80))
# # 注意参数是一个tuple，包含地址和端口号。
# # 建立TCP连接后,我们就可以向新浪服务器发送请求,要求返回首页的内容:
# # 发送数据
# s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
# buffer = []
# while True:
#     # 每次最多接受1k字节
#     d = s.recv(1024)
#     if d:
#         buffer.append(d)
#     else:
#         break
# data = b''.join(buffer)
#
# # 关闭连接
# s.close()
#
# #监听接口
# s.bind(('127.0.0.1', 9988))
# # 紧接着,调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量:
# s.listen(5)
# print('Waiting for connection...')
# # 接下来,服务器程序通过一个永久的循环来接受来自客户端连接,accept()会等待并返回一个客户端的连接
# while True:
#     # 接受一个新连接
#     sock, addr = s.accept()
#     # 创建新线程来处理TCP连接:
#     t = threading.Thread(target=tcplink, args=(sock, addr))
#     t.start()
#
# # 每个连接都必须创建新线程(或进程)来处理,否则,单线程在处理连接的过程中,无法接受其他客户端的链接.
# def tcplick(sock, addr):
#     print('Accept new connection from %s:%s...' % addr)
#     sock.send(b'Welcome!')
#     while True:
#         data = sock.recv(1024)
#         time.sleep(1)
#         if not data or data.decode('utf-8') == 'exit':
#             break
#         sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
#         print('Connection from %s:%s closed.' % addr)
#
# # 连接建立后 ,服务器首先发送一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。如果客户端发送了exit字符串，就直接关闭连接。
# #
# # 要测试这个服务器程序，我们还需要编写一个客户端程序：

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
    # 发送数据:
    s.send(data)
    print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

# 使用UDP协议时，不需要建立连接，只需要知道对方的IP地址和端口号，就可以直接发数据包。但是，能不能到达就不知道了。
# 虽然用UDP传输数据不可靠，但它的优点是和TCP比，速度快，对于不要求可靠到达的数据，就可以使用UDP协议。
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口
s.bind('127.0.0.1', 9999)
# 创建Socket时，SOCK_DGRAM指定了这个Socket的类型是UDP。绑定端口和TCP一样，但是不需要调用listen()方法，而是直接接收来自任何客户端的数据：
print('Bind UDP on 9999...')
while True:
    # 接受数据
    data, addr = s.recvfrom(1024)
    print('Received from %s:%s.' % addr)
    s.sendto(b'Hello, %s!' % data, addr)